package com.spark.common.net

import android.os.*
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import okio.*
import java.io.IOException
import java.text.NumberFormat

object ProgressBody {
    val handler: Handler = Handler(Looper.getMainLooper())

    fun request(requestBody: RequestBody, progressListener: ProgressListener, refreshTime: Long): RequestBody {
        var progressInfo = ProgressInfo(id = System.currentTimeMillis())
        return object : RequestBody() {
            override fun contentType(): MediaType? {
                return requestBody.contentType()
            }

            override fun contentLength(): Long {
                return requestBody.contentLength()
            }

            override fun writeTo(sink: BufferedSink?) {
                val bufferSink = Okio.buffer(RequestCountingSink(sink, progressInfo, progressListener, contentLength(), refreshTime))
                try {
                    requestBody.writeTo(bufferSink)
                    bufferSink.flush()
                } catch (e: IOException) {
                    progressListener.error(progressInfo.id, e)
                    throw e
                }
            }
        }
    }

    fun response(responseBody: ResponseBody?, progressListener: ProgressListener?, refreshTime: Long): ResponseBody {
        var progressInfo = ProgressInfo(id = System.currentTimeMillis())
        return object : ResponseBody() {
            override fun contentLength(): Long {
                return responseBody?.contentLength() ?: 0
            }

            override fun contentType(): MediaType? {
                return responseBody?.contentType()
            }

            override fun source(): BufferedSource {
                return Okio.buffer(ResponseCountingSource(responseBody?.source(), progressInfo, progressListener, contentLength(), refreshTime))
            }

        }
    }


    private class RequestCountingSink(delegate: BufferedSink?,
                                      private var progressInfo: ProgressInfo,
                                      private var progressListener: ProgressListener,
                                      private var contentLength: Long,
                                      private var refreshTime: Long) : ForwardingSink(delegate) {
        var totalBytesRead: Long = 0
        var lastRefreshTime: Long = 0
        var tempSize: Long = 0

        override fun write(source: Buffer?, byteCount: Long) {
            try {
                super.write(source, byteCount)
            } catch (e: IOException) {
                e.printStackTrace()
                progressListener.error(progressInfo.id, e)
                throw e
            }

            if (progressInfo.contentLength <= 0) {
                progressInfo.contentLength = contentLength
            }

            totalBytesRead += byteCount
            tempSize += byteCount
            var currentTime: Long = SystemClock.elapsedRealtime()

            if (currentTime - lastRefreshTime >= refreshTime || totalBytesRead == progressInfo.contentLength) {
                var finalTempSize = tempSize
                var finalTotalBytesRead = totalBytesRead
                var finalIntervalTime = currentTime - lastRefreshTime

                handler.post {
                    progressInfo.intervalBytes = finalIntervalTime
                    progressInfo.currentBytes = finalTotalBytesRead
                    progressInfo.intervalTime = finalIntervalTime
                    progressInfo.isFinish = finalTotalBytesRead == progressInfo.contentLength

                    progressListener.update(progressInfo)
                }

                lastRefreshTime = currentTime
                tempSize = 0
            }
        }
    }

    class ResponseCountingSource(
            delegate: BufferedSource?,
            private var progressInfo: ProgressInfo,
            private var progressListener: ProgressListener?,
            private var contentLength: Long,
            private var refreshTime: Long) : ForwardingSource(delegate) {

        var totalBytesRead: Long = 0
        var lastRefreshTime: Long = 0
        var tempSize: Long = 0

        override fun read(sink: Buffer?, byteCount: Long): Long {
            var bytesRead: Long = 0L
            try {
                bytesRead = super.read(sink, byteCount)
            } catch (e: IOException) {
                e.printStackTrace()
                progressListener?.error(progressInfo.id, e)
                throw e
            }

            if (progressInfo.contentLength <= 0) {
                progressInfo.contentLength = contentLength
            }

            totalBytesRead += if (bytesRead.toInt() != -1) {
                bytesRead
            } else {
                0
            }

            var currentTime = SystemClock.elapsedRealtime()
            if (currentTime - lastRefreshTime >= refreshTime || bytesRead.toInt() == -1 || totalBytesRead == progressInfo.contentLength) {
                var finalBytesRead = bytesRead
                var finalTempSize = tempSize
                var finalTotalBytesRead = totalBytesRead
                var finalIntervalTime = currentTime - lastRefreshTime

                handler.post {
                    progressInfo.intervalTime = finalIntervalTime
                    progressInfo.currentBytes = finalTotalBytesRead
                    if (finalBytesRead.toInt() == -1) {
                        progressInfo.intervalBytes = -1
                    } else {
                        progressInfo.intervalBytes = finalTempSize
                    }
                    progressInfo.isFinish = finalBytesRead.toInt() == -1 && finalTotalBytesRead == progressInfo.contentLength

                    progressListener?.update(progressInfo)
                }
                lastRefreshTime = currentTime
                tempSize = 0
            }
            return bytesRead
        }
    }

    interface ProgressListener {
        fun update(progressInfo: ProgressInfo)
        fun error(id: Long, e: Exception)
    }

    data class ProgressInfo(
            var currentBytes: Long = 0,
            var contentLength: Long = 0,
            var intervalTime: Long = 0,
            var intervalBytes: Long = 0,
            var id: Long = 0,
            var isFinish: Boolean = false

    ) : Parcelable {
        var numberInstance: NumberFormat = NumberFormat.getNumberInstance()
        /**
         * 进度
         */
        fun getPercent(): Int {
            if (currentBytes <= 0 || contentLength <= 0) {
                return 0
            }
            return (100 * currentBytes / contentLength).toInt()
        }

        /**
         * 进度
         * @param precision 精度
         */
        fun getPercent(precision: Int): Float {
            if (currentBytes <= 0 || contentLength <= 0) {
                return 0F
            }
            numberInstance.maximumFractionDigits = precision
            val result = 100 * currentBytes / this.contentLength
            return numberInstance.format(result).toFloat()

        }

        override fun toString(): String {
            return "ProgressInfo(currentBytes=$currentBytes, contentLength=$contentLength, intervalTime=$intervalTime, intervalBytes=$intervalBytes, id=$id, isFinish=$isFinish, numberInstance=$numberInstance)"
        }

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            parcel.writeLong(this.currentBytes)
            parcel.writeLong(this.contentLength)
            parcel.writeLong(this.intervalTime)
            parcel.writeLong(this.intervalBytes)
            parcel.writeLong(this.id)
            if (this.isFinish) {
                parcel.writeByte(0.toByte())
            } else {
                parcel.writeByte(1.toByte())
            }

        }


        override fun describeContents(): Int {
            return 0
        }


        companion object {
            fun parse(parcelIn: Parcel): ProgressInfo {
                return ProgressInfo(
                        currentBytes = parcelIn.readLong(),
                        contentLength = parcelIn.readLong(),
                        intervalTime = parcelIn.readLong(),
                        intervalBytes = parcelIn.readLong(),
                        id = parcelIn.readLong(),
                        isFinish = parcelIn.readByte().toInt() != 0
                )
            }

            var CREATOR = object : Parcelable.Creator<ProgressInfo> {
                override fun createFromParcel(parcel: Parcel): ProgressInfo {
                    return parse(parcel)
                }

                override fun newArray(size: Int): Array<ProgressInfo?> {
                    return arrayOfNulls(size)
                }
            }
        }
    }
}